
#include <algorithm>
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <fcntl.h>
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <vector>

static int const ITERATIONS = 10000;
static char const* const DOMAIN_SOCKET_NAME = "/tmp/foobar";
static int const PORT = 5002;

static int perc(std::vector<double> const& v, int percent);
static void run_receiver(int packet_size, int packet_count, bool use_tcp);
static void run_sender(int packet_size, int packet_count, bool use_tcp);

int main(int argc, char const** argv)
{
    char buffer[2048];
    memset(buffer, 0, sizeof(buffer));

    // Arguments
    if (argc != 5)
    {
        std::cerr << "ARGUMENTS: <packet size> <packet count> <tcp|unix> <send|receive>\n";
        return -1;
    }
    int const packet_size = atoi(argv[1]);
    if (packet_size < sizeof(struct timespec))
    {
        std::cerr << "packet size too small\n";
        return -1;
    }
    int const packet_count = atoi(argv[2]);
    bool tcp_socket;
    if (strcmp(argv[3], "tcp") == 0)
        tcp_socket = true;
    else if (strcmp(argv[3], "unix") == 0)
        tcp_socket = false;
    else
    {
        std::cerr << "invalid <tcp|unix> argument\n";
        return -1;
    }
    bool is_sender;
    if (strcmp(argv[4], "send") == 0)
        is_sender = true;
    else if (strcmp(argv[4], "receive") == 0)
        is_sender = false;
    else
    {
        std::cerr << "invalid <send|receive> argument\n";
        return -1;
    }

    if (is_sender)
        run_sender(packet_size, packet_count, tcp_socket);
    else
        run_receiver(packet_size, packet_count, tcp_socket);

    return 0;
}

static int perc(std::vector<double> const& v, int percent)
{
    return static_cast<int>(v[static_cast<size_t>((percent / 100.0) * v.size())]);
}

static void run_receiver(int packet_size, int packet_count, bool use_tcp)
{
    struct sockaddr_in tcp_addr;
    struct sockaddr_un unix_addr;
    char buffer[2048];

    int fd;
    if (use_tcp)
    {
        fd = socket(AF_INET, SOCK_STREAM, 0);
        memset(&tcp_addr, 0, sizeof(tcp_addr));
        tcp_addr.sin_family = AF_INET;
        tcp_addr.sin_addr.s_addr = INADDR_ANY;
        tcp_addr.sin_port = htons(PORT);
        bind(fd, (struct sockaddr*)&tcp_addr, sizeof(tcp_addr));
    }
    else
    {
        remove(DOMAIN_SOCKET_NAME);
        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        memset(&unix_addr, 0, sizeof(unix_addr));
        unix_addr.sun_family = AF_UNIX;
        strcpy(unix_addr.sun_path + 1, DOMAIN_SOCKET_NAME);
        bind(fd, (struct sockaddr*)&unix_addr, sizeof(unix_addr));
    }
    listen(fd,5);
    int client_fd = accept(fd, NULL, NULL);
    std::vector<double> delays;
    delays.reserve(100000);
    struct timespec recv_time;
    while (true)
    {
        int bytes = read(client_fd, buffer, sizeof(buffer));
        clock_gettime(CLOCK_MONOTONIC, &recv_time);
        if (bytes <= 0)
            break;
        struct timespec* send_time = (struct timespec*)buffer;
        delays.push_back(((recv_time.tv_sec - send_time->tv_sec) * 1000000000.0) + (recv_time.tv_nsec - send_time->tv_nsec));
    }
    std::sort(delays.begin(), delays.end());
    printf("50=%dns 75:%dns 90:%dns 95:%dns 99:%dns\n", perc(delays,50), perc(delays,75),
           perc(delays,90), perc(delays,95), perc(delays,99));
}

static void run_sender(int packet_size, int packet_count, bool use_tcp)
{
    struct sockaddr_in tcp_addr;
    struct sockaddr_un unix_addr;
    char buffer[2048];

    int fd;
    if (use_tcp)
    {
        fd = socket(AF_INET, SOCK_STREAM, 0);
        memset(&tcp_addr, 0, sizeof(tcp_addr));
        tcp_addr.sin_family = AF_INET;
        tcp_addr.sin_port = htons(PORT);
        //inet_pton(AF_INET, "127.0.0.1", &tcp_addr.sin_addr);
        inet_pton(AF_INET, "172.16.195.164", &tcp_addr.sin_addr);
        connect(fd, (struct sockaddr*)&tcp_addr, sizeof(tcp_addr));
    }
    else
    {
        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (0 == fd)
        {
            std::cerr << "failed to create socket\n";
            return;
        }
        memset(&unix_addr, 0, sizeof(unix_addr));
        unix_addr.sun_family = AF_UNIX;
        strcpy(unix_addr.sun_path + 1, DOMAIN_SOCKET_NAME);
        if (connect(fd, (struct sockaddr*)&unix_addr, sizeof(unix_addr)) == -1)
        {
            std::cerr << "connect failed!\n";
            return;
        }
    }
    memset(buffer, 0, sizeof(buffer));
    struct timespec sleep_spec = { 0, 1000000 };
    struct timespec after;
    std::vector<double> send_times;
    send_times.reserve(100000);
    while (packet_count--)
    {
        nanosleep(&sleep_spec, NULL);
        struct timespec* before = (struct timespec*)buffer;
        clock_gettime(CLOCK_MONOTONIC, before);
        if (write(fd, buffer, packet_size) != packet_size)
        {
            std::cerr << "bad write!\n";
            return;
        }
        clock_gettime(CLOCK_MONOTONIC, &after);
        send_times.push_back(((after.tv_sec - before->tv_sec) * 1000000000.0) + (after.tv_nsec - before->tv_nsec));
    }
    std::sort(send_times.begin(), send_times.end());
    printf("50=%dns 75:%dns 90:%dns 95:%dns 99:%dns\n", perc(send_times,50), perc(send_times,75),
           perc(send_times,90), perc(send_times,95), perc(send_times,99));
}


